interface IUploaderOption {
    /**目标元素 */
    el: string | Element;
    /**后台响应的地址 */
    url: string;
    /**html模板路径 */
    tmplPath: string;
    /**向服务器提交的额外参数， 不能包含"files"属性！ */
    data?: null;
    /**文件类型过滤器 */
    filter?: string[];
}
interface IFileState {
    file: File;
    status: number;  //四种状态：未上传 0，上传中 1，暂停 2  已上传 3，失败 4
    request?: XMLHttpRequest;
}
/**文件上传 支持IE11+ ,CHROME,FF*/
class Uploader {
    /**默认参数 */
    el: Element;
    url: string;
    tmplPath: string;
    data: any;
    filter: string[];
    colWidth: number[] = [40, 150, 70, 100, 150, 70, 40];
    arrStatus = ["未上传", "上传中", "已暂停", "已上传", "失败"];
    /**已经添加入的文件状态数组 */
    arrFiles: IFileState[] = [];
    $file: HTMLInputElement;
    $tbody: HTMLElement;
    $btnUpload: HTMLButtonElement;
    $btnCancel: HTMLButtonElement;
    $btnDelAll: HTMLButtonElement;
    /**拖放入区域后，在离开时的定时器 */
    _leaveTimer: number;
    constructor(opt: IUploaderOption) {
        var defOpt = {
            el: "", url: "", tmplPath: "/scripts/uploader.html",
            data: null, filter: null
        }
        for (var o in defOpt) {
            this[o] = opt[o] ? opt[o] : defOpt[o];
        }
        this.init()
    }
    init() {
        if (typeof this.el === "string") this.el = document.querySelector(this.el);
        if (!this.el || !this.url) {
            alert("参数不正确！"); return;
        }
        //请求模板后，插入到页面，再绑定事件

        this.el.innerHTML =
            `<div class="up-wrapper">
    <div class="up-tools">
        <div class="up-add">
            <button class="up-button">添加文件</button>
            <input type="file" class="up-file" multiple>
        </div>
        <button class="up-button">上传</button>
        <button class="up-button">停止上传</button>
        <button class="up-button">删除所有</button>
    </div>
    <div>
        <table class="up-table">
            <colgroup>
                <col>
                <col>
                <col>
                <col>
                <col>
                <col>
                <col>
            </colgroup>
            <thead>
                <tr>
                    <th>序号</th>
                    <th>文件名</th>
                    <th>类型</th>
                    <th>大小</th>
                    <th>进度</th>
                    <th>状态</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
            </tbody>
        </table>
    </div>
</div>`;
        Array.prototype.slice.call(this.el.querySelectorAll("col")).forEach((col, idx) => {
            col.style.width = this.colWidth[idx] + "px";
        });
        this.$file = this.el.querySelector(".up-file") as HTMLInputElement;
        this.$tbody = this.el.querySelector("tbody") as HTMLElement;
        this.$btnUpload = this.$file.parentElement.nextElementSibling as HTMLButtonElement;
        this.$btnCancel = this.$btnUpload.nextElementSibling as HTMLButtonElement;
        this.$btnDelAll = this.$btnCancel.nextElementSibling as HTMLButtonElement;
        this.bindEvent();

    }
    bindEvent() {
        //四个按钮事件
        this.$file.addEventListener("change", this.addAll.bind(this), false)
        this.$btnUpload.addEventListener("click", this.uploadAll.bind(this), false)
        this.$btnCancel.addEventListener("click", this.abortAll.bind(this), false)
        this.$btnDelAll.addEventListener("click", this.delAll.bind(this), false)
        /*删除单个文件*/
        this.$tbody.addEventListener("click", this.delOneFile.bind(this), false)
        /**拖放事件绑定 */
        this.el.addEventListener("dragenter", this.dragEnter.bind(this), false);
        this.el.addEventListener("dragover", this.dragOver.bind(this), false);
        this.el.addEventListener("dragleave", this.dragLeave.bind(this), false);
        this.el.addEventListener("drop", this.dragDrop.bind(this), false);
    }
    /**以下为事件 */
    //添加文件时
    addAll() {
        this.addFileListToQueue(this.$file.files)
    }
    //上传按钮点击 
    uploadAll() {
        this.arrFiles.forEach(fs => {
            if (fs.status == 0 || fs.status == 2) {
                this.upload(fs);
                this.setRowStatebyfs(fs, 1)
            }
        })
    }
    //停止上传按钮点击 
    abortAll() {
        this.arrFiles.forEach(fs => {
            if (fs.status == 1) {
                fs.request.abort();
                this.setRowStatebyfs(fs, 2);
            }
        })
    }
    //删除按钮点击
    delAll() {
        this.abortAll();
        this.arrFiles.length = 0;
        this.$tbody.innerHTML = ''
    }
    delOneFile(ev: MouseEvent) {
        var tar = ev.target as Element;
        if (tar.classList.contains("up-file-del")) {
            var tr = tar.parentElement.parentElement as HTMLTableRowElement,
                idx = tr.rowIndex - 1;  //rowindex是从1开始
            this.arrFiles.splice(idx, 1);
            tr.remove();
            this.resetNo();
        }
    }
    /**拖入文件事件 */
    dragEnter(e: DragEvent) {
        console.log("enter")
        e.preventDefault();
        if (e.currentTarget == this.el) this.el.classList.add("up-drag");
    }
    dragOver(e: DragEvent) {
        console.log("over")
        e.preventDefault();
        clearTimeout(this._leaveTimer);
    }
    dragLeave(e: DragEvent) {
        console.log("leave")
        e.preventDefault();
        clearTimeout(this._leaveTimer);
        this._leaveTimer = setTimeout(() =>
            this.el.classList.remove("up-drag"), 100)
    }
    dragDrop(e: DragEvent) {
        console.log("drop")
        e.preventDefault();
        this.addFileListToQueue(e.dataTransfer.files);
        this.el.classList.remove("up-drag");
    }
    /**上传事件 */
    addOnSuccess(fs: IFileState) {
        this.setRowStatebyfs(fs, 3)
    }
    addOnError(fs: IFileState) {
        this.setRowStatebyfs(fs, 4)
    }
    addOnPregress(fs: IFileState, e: ProgressEvent) {
        var per = Math.min(Math.round((e.loaded / e.total) * 100), 100) + "%";
        var row = this.findRowbyfs(fs);
        if (row) {
            var bar = row.querySelector(".up-pg-bar") as HTMLDivElement;
            bar.style.background = `linear-gradient(to right, #5cb85c ${per},#fff 0)`;
            bar.nextElementSibling.textContent = per;
        }
    }
    /**以下工具函数  */
    checkFile(filename: string): boolean {
        return !this.filter || new RegExp(this.filter.map(f => `(${f})$`).join('|')).test(filename)
    }
    getExt(file: File) {
        var pos = file.name.lastIndexOf(".")
        return pos >= 0 ? file.name.substr(pos) : "未知";
    }
    humanize(size) {
        var units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
        var ord = Math.floor(Math.log(size) / Math.log(1024));
        ord = Math.min(Math.max(0, ord), units.length - 1);
        var s = Math.round((size / Math.pow(1024, ord)) * 100) / 100;
        return s + ' ' + units[ord];
    }
    findRowbyfs(fs: IFileState) {
        var pos = this.arrFiles.indexOf(fs);
        return pos >= 0 ? this.$tbody.querySelectorAll("tr").item(pos) : null;
    }
    setRowStatebyfs(fs: IFileState, status: number) {
        fs.status = status;
        var row = this.findRowbyfs(fs);
        if (row) { row.children[5].textContent = this.arrStatus[status] }
    }
    /**重设表的编号 */
    resetNo() {
        var children = this.$tbody.children, cnt = children.length;
        for (var i = 0; i < cnt; i++) children[i].children[0].textContent = (i + 1) as any;
    }

    upload(fs: IFileState) {
        fs.request = new XMLHttpRequest();
        fs.request.open("post", this.url);
        /**进度事件 */
        fs.request.upload.onprogress = (e: ProgressEvent) => this.addOnPregress(fs, e);
        /**请求事件 */
        fs.request.onload = () => {
            if (fs.request.status >= 200 && fs.request.status < 400) this.addOnSuccess(fs);
            else this.addOnError(fs);
        };
        fs.request.onerror = () => this.addOnError(fs);

        var fd = new FormData();
        fd.append("files", fs.file);
        if (this.data) {
            for (var k in this.data) fd.append(k, this.data[k])
        }
        fs.request.send(fd);
    }
    addFileListToQueue(filelist: FileList) {
        var files: File[] = Array.prototype.slice.call(filelist);
        if (!files.length) return;
        files.forEach(f => {
            //通过验证添加到表格中
            this.checkFile(f.name) && this.addFileToQueue(f)
        })
    }
    //添加文件到队列尾，并显示到表格
    addFileToQueue(file: File) {
        var found = false;
        this.arrFiles.forEach(fs => {
            if (fs.file.name == file.name && fs.file.size == file.size)
                found = true;
        })
        if (!found) {
            this.$tbody.insertAdjacentHTML("beforeend",
                `<tr>
    <td>${this.arrFiles.length + 1}</td>
    <td>${file.name}</td>
    <td>${this.getExt(file)}</td>
    <td>${this.humanize(file.size)}</td>
    <td><div class="up-pg">
            <div class="up-pg-bar"></div>
            <div class="up-pg-value"></div>
        </div>
    </td>
    <td class="up-center">${"未上传"}</td>
    <td class="up-center">
        <a href="#" class="up-file-del">${"删除"}</a>
    </td>
</tr>`)
            this.arrFiles.push({ file, status: 0 })
        }
    }
}